package task

import (
	"time"
)

// 任务执行时间的周期类型
type taskIntervalType int

const (
	taskIntervalFixed         taskIntervalType = iota // 一个固定的时间间隔（time.Duration）
	taskIntervalMonthDay                              // 每月的某一日
	taskIntervalMonthFirstDay                         // 每月的第一日
	taskIntervalMonthLastDay                          // 每月的最后一日
	taskIntervalWeekDay                               // 每周的某一日（周一至周日）
)

func (iType taskIntervalType) String() string {
	switch iType {
	case taskIntervalFixed:
		return "fixed:一个固定的时间间隔"
	case taskIntervalMonthDay:
		return "mouthDay:每月的某一日"
	case taskIntervalMonthFirstDay:
		return "mouthFirstDay:每月的第一日"
	case taskIntervalMonthLastDay:
		return "mouthLastDay:每月的最后一日"
	case taskIntervalWeekDay:
		return "weekDay:每周的某一日（周一至周日）"
	default:
		return "unknown"
	}
}

// 格式化任务执行时间的周期类型
func taskIntervalParse(iType taskIntervalType) taskIntervalType {
	switch iType {
	case taskIntervalFixed:
		return iType
	case taskIntervalMonthDay:
		return iType
	case taskIntervalMonthFirstDay:
		return iType
	case taskIntervalMonthLastDay:
		return iType
	case taskIntervalWeekDay:
		return iType
	default:
		panic("unknown taskIntervalType")
	}
}

type taskInterval struct {
	iType       taskIntervalType
	year        int        //当前 年
	month       time.Month //当前 月
	day         int        //当前 日
	hour        int        //当前 时
	minute      int        //当前 分
	second      int        //当前 秒
	millisecond int        //当前 毫秒
	weekDay     int        //单前 周几
	startAt     time.Time
	executeAt   time.Time
	duration    time.Duration
}

// newTaskIntervalWithStartAt 生成一个新的任务执行时间的周期对象
//   - iType: 任务执行时间的周期类型
//   - interval: 时间间隔（仅在 iType 为 taskIntervalFixed 时有效）
//   - startAt: 定时任务开始执行时间(依据iType从开始时间自动解析出具体的执行时间)
func newTaskIntervalWithStartAt(iType taskIntervalType, interval time.Duration, startAt time.Time) *taskInterval {
	if startAt.IsZero() {
		// startAt 无效时，将基于当前时间生成
		startAt = time.Now()
	} else if startAt.Location() != time.Local {
		startAt = startAt.Local()
	}
	return newTaskIntervalWithDate(iType, interval, startAt.Year(), int(startAt.Month()), startAt.Day(), startAt.Hour(), startAt.Minute(), startAt.Second(), startAt.Nanosecond()/1e6, int(startAt.Weekday()))
}

// newTaskIntervalWithDate 生成一个新的任务执行时间的周期对象
//   - iType: 任务执行时间的周期类型
//   - interval: 时间间隔（仅在 iType 为 taskIntervalFixed 时有效）
//   - year, month, day: 年/月/日
//   - hour, minute, second,millisecond: 时/分/秒/毫秒
//   - weekDay: 周几 (0-7, 0或7 => 星期日) 仅在 iType 为 taskIntervalWeekDay 时有效
func newTaskIntervalWithDate(iType taskIntervalType, interval time.Duration, year, month, day, hour, minute, second, millisecond, weekDay int) *taskInterval {
	nowTime := time.Now()
	iType = taskIntervalParse(iType)

	// 毫秒级任务, 最小为1毫秒 ( 与TaskScheduled:minCheckPeriod 相同 )
	if iType == taskIntervalFixed && interval < time.Millisecond {
		interval = time.Millisecond
	}
	if year < nowTime.Year() {
		year = nowTime.Year()
	}
	if month < 1 {
		month = 1
	} else if month > 12 {
		month = 12
	}
	switch iType {
	case taskIntervalFixed, taskIntervalWeekDay:
		if day < 1 {
			day = 1
		} else if day > 31 {
			day = 31
		} else {
			days := daysInMonth(year, time.Month(month))
			if day > days {
				day = days
			}
		}
	case taskIntervalMonthDay:
		if day < 1 {
			day = 1
		} else if day > 31 {
			day = 31
		}
	case taskIntervalMonthFirstDay:
		day = 1
	case taskIntervalMonthLastDay:
		day = daysInMonth(year, time.Month(month))
	}

	if hour < 0 {
		hour = 0
	} else if hour > 23 {
		hour = 23
	}
	if minute < 0 {
		minute = 0
	} else if minute > 59 {
		minute = 59
	}
	if second < 0 {
		second = 0
	} else if second > 59 {
		second = 59
	}
	if millisecond < 0 {
		millisecond = 0
	} else if millisecond > 999 {
		millisecond = 999
	}
	if weekDay <= 0 || weekDay > 6 {
		weekDay = 7
	}
	ti := &taskInterval{
		iType:       iType,
		year:        year,
		month:       time.Month(month),
		day:         day,
		hour:        hour,
		minute:      minute,
		second:      second,
		millisecond: millisecond,
		weekDay:     weekDay,
		startAt:     time.Date(year, time.Month(month), day, hour, minute, second, millisecond*1e6, time.Local),
		executeAt:   time.Time{},
		duration:    interval,
	}
	// 创建时计算第一次任务执行时间
	ti.calculating()
	return ti
}

// daysInMonth 返回指定年份和月份的天数。
// year: 指定的年份。
// month: 指定的月份。
// 返回值: 指定月份的天数。
func daysInMonth(year int, month time.Month) int {
	// 使用time.Date创建下一个月的第0天(也就是这个月的最后一天)时间对象，然后提取其中的天数，即可得到当前月的天数。
	return time.Date(year, month+1, 0, 0, 0, 0, 0, time.Local).Day()
}

// calculating 重新计算任务时间
func (t *taskInterval) calculating() {
	var (
		startAt   time.Time
		executeAt time.Time
	)
	if t.executeAt.IsZero() {
		startAt = t.startAt
	} else {
		startAt = t.executeAt
	}

	switch t.iType {
	case taskIntervalFixed: // 一个固定的时间间隔（time.Duration）
		executeAt = t.calculatingFixed(startAt)
	case taskIntervalMonthDay: // 每月的某一日
		executeAt = t.calculatingMonthDay(startAt)
	case taskIntervalMonthFirstDay: // 每月的第一日
		executeAt = t.calculatingMonthFirstDay(startAt)
	case taskIntervalMonthLastDay: // 每月的最后一日
		executeAt = t.calculatingMonthLastDay(startAt)
	case taskIntervalWeekDay: // 每周的某一日（周一至周日）
		executeAt = t.calculatingWeekDay(startAt)
	default:
		panic("taskIntervalType error")
	}

	//统一更新时间
	//更新年、月、日
	t.year = executeAt.Year()
	t.month = executeAt.Month()
	if t.iType != taskIntervalMonthDay {
		t.day = executeAt.Day()
	}
	t.hour = executeAt.Hour()
	t.minute = executeAt.Minute()
	t.second = executeAt.Second()
	t.millisecond = executeAt.Nanosecond() / 1e6

	switch t.iType {
	case taskIntervalMonthDay, taskIntervalMonthFirstDay, taskIntervalMonthLastDay, taskIntervalWeekDay:
		t.executeAt = executeAt
		t.duration = time.Until(executeAt)
	default:
		// taskIntervalFixed
		t.executeAt = executeAt // 仅更新时间，间隔保持不变
	}
}

// calculatingFixed 计算固定间隔的执行时间
func (t *taskInterval) calculatingFixed(startAt time.Time) time.Time {
	executeAt := startAt.Add(t.duration)
	nowTime := time.Now()
	for executeAt.Before(nowTime) {
		executeAt = executeAt.Add(t.duration)
	}
	return executeAt
}

// calculatingMonthDay 计算每月的某一日的具体执行时间
func (t *taskInterval) calculatingMonthDay(startAt time.Time) time.Time {
	// 基准时间（本月的第一日）
	executeAt := time.Date(startAt.Year(), startAt.Month(), 1, t.hour, t.minute, t.second, t.millisecond*1e6, time.Local)
	// 检查day是否有效
	nowDay := executeAt.Day()
	if t.day != nowDay {
		days := daysInMonth(executeAt.Year(), executeAt.Month())
		if t.day > days {
			executeAt = executeAt.AddDate(0, 0, days-nowDay)
		} else {
			executeAt = executeAt.AddDate(0, 0, t.day-nowDay)
		}
	}
	// 当前时间已过了指定的日期，滚动到下一月
	nowTime := time.Now()
	for executeAt.Before(nowTime) {
		executeAt = executeAt.AddDate(0, 1, 0)
	}
	return executeAt
}

// calculatingMonthFirstDay 计算每月的第一日的具体执行时间
func (t *taskInterval) calculatingMonthFirstDay(startAt time.Time) time.Time {
	//基准时间（本月的第一日）
	t.year = startAt.Year()
	t.month = startAt.Month()
	executeAt := time.Date(t.year, t.month, 1, t.hour, t.minute, t.second, t.millisecond*1e6, time.Local)
	//当前时间已过，滚动到下一月
	nowTime := time.Now()
	for executeAt.Before(nowTime) {
		if t.month == 12 {
			t.month = 1
			t.year++
		} else {
			t.month++
		}
		executeAt = time.Date(t.year, t.month, 1, t.hour, t.minute, t.second, t.millisecond*1e6, time.Local)
	}
	return executeAt
}

// calculatingMonthLastDay 计算每月的最后一日的具体执行时间
func (t *taskInterval) calculatingMonthLastDay(startAt time.Time) time.Time {
	//基准时间（本月的最后一日）
	t.year = startAt.Year()
	t.month = startAt.Month()
	executeAt := time.Date(t.year, t.month+1, 0, t.hour, t.minute, t.second, t.millisecond*1e6, time.Local)
	//当前时间已过，滚动到下一月
	nowTime := time.Now()
	for executeAt.Before(nowTime) {
		if t.month == 12 {
			t.month = 1
			t.year++
		} else {
			t.month++
		}
		executeAt = time.Date(t.year, time.Month(t.month), 0, t.hour, t.minute, t.second, t.millisecond*1e6, time.Local)
	}
	return executeAt
}

// calculatingWeekDay 计算每周的某一日（周一至周日）的具体执行时间
func (t *taskInterval) calculatingWeekDay(startAt time.Time) time.Time {
	if t.weekDay < 1 || t.weekDay > 6 {
		t.weekDay = 7
	}
	nowWeek := int(startAt.Weekday())
	if nowWeek == 0 {
		nowWeek = 7
	}
	//基准时间
	executeAt := time.Date(startAt.Year(), startAt.Month(), startAt.Day(), t.hour, t.minute, t.second, t.millisecond*1e6, time.Local)
	//计算周几
	if nowWeek == t.weekDay {
		if executeAt.Before(time.Now()) {
			executeAt = executeAt.Add(time.Hour * 24 * 7)
		}
	} else if nowWeek > t.weekDay {
		// 今天周2 周1运行 => 加6天   7-(周2)+(周1) = 6
		// 今天周5 周3运行 => 加5天   7-(周5)+(周3) = 5
		// 今天周7 周3运行 => 加3天   7-(周7)+(周3) = 3
		executeAt = executeAt.AddDate(0, 0, 7-nowWeek+t.weekDay)
	} else {
		// 今天周2 周5运行 => 加3天   5-2
		// 今天周3 周5运行 => 加2天   5-3
		executeAt = executeAt.AddDate(0, 0, t.weekDay-nowWeek)
	}
	return executeAt
}

// ExecuteAt 取得任务执行时间对象
func (t *taskInterval) ExecuteAt() time.Time {
	if t.executeAt.Before(time.Now()) {
		//计算或更新任务执行时间
		t.calculating()
	}
	return t.executeAt
}

// Duration 取得任务执行时间间隔对象
func (t *taskInterval) Duration() time.Duration {
	return t.duration
}

// Type 取得任务执行时间的周期类型
func (t *taskInterval) Type() taskIntervalType {
	return t.iType
}
